package edu.kit.aifb.ldsbench.generator2;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryException;

import edu.kit.aifb.ldsbench.cli2.CLI;
import edu.kit.aifb.ldsbench.generator.opt.util2.DoubleInstanceBuffer;
import edu.kit.aifb.ldsbench.generator.opt2.IOptimizationSolver;
import edu.kit.aifb.ldsbench.generator.opt2.PropertyOptimizer;
import edu.kit.aifb.ldsbench.generator.util2.IDistributionValues;
import edu.kit.aifb.ldsbench.generator.util2.Octave;
import edu.kit.aifb.ldsbench.out2.FileOutput;
import edu.kit.aifb.ldsbench.out2.IGeneratorOutput;

public class Generator implements Runnable{

	// size for the instance set and jitter value cache. IF numEvents is smaller then the value of numEvents is chosen.
	// note this buffer size is created twice.
	private static final int INSTANCE_BUFFER_SIZE = 1;
	private static final int JITTER_BUFFER_SIZE = 1;
	private static final int EVENT_SIZE_BUFFER_SIZE = 1;
	
	private int numEvents;
	
	private IDistributionValues distribValues;
	private IOptimizationSolver optSolver;
	private PropertyOptimizer propOptimizer;
	private IGeneratorOutput out;
	private DoubleInstanceBuffer instanceBuffer;
	private boolean plotMode;
	private String dir;
	
	public Generator(int numEvents, IGeneratorOutput out, boolean plotMode, String dir){
		this.plotMode = plotMode;
		this.dir = dir;
		this.numEvents = numEvents;
		this.out = out;
		Octave octave = new Octave();
		this.distribValues = octave;
		this.optSolver = octave;
		this.instanceBuffer = new DoubleInstanceBuffer(INSTANCE_BUFFER_SIZE);
		try {
			this.propOptimizer = new PropertyOptimizer(CLI.REPO.getConnection(), octave, octave, CLI.STATS);
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		
	}

	@Override
	public void run() {
		//starting and triggering the buffers
		this.instanceBuffer.start();
		
		if(this.plotMode){
			if(this.dir == null) throw new IllegalArgumentException("Directory should be set");
			
			double eventFactor = 1d;
			double linkageDegree = 1d;
			double coherence = 1d;
			
			IGeneratorOutput output;
			String name;
			
			//modify side conditions
			for(int i = 0; i < 4; i++){
				for(int j = 0; j < 4; j++){
					for(int k = 0; k < 4; k++){
						//each parameter combination gets its own output
			//			name = plotFileName(eventFactor, linkageDegree, coherence);
			//			output = new FileOutput(this.dir, name);
						//modify parameters
						CLI.STATS.setLinkageDegree(linkageDegree);
						CLI.STATS.setStructuredness(coherence);
						//generate
						generateEvents(dir, eventFactor, linkageDegree, coherence);
						
			//			output.close();
						coherence -= 0.25d;
					}
					linkageDegree -= 0.25d;
					coherence = 1d;
				}
				linkageDegree = 1d;
				coherence = 1d;
				eventFactor -= 0.25d;
			}
			
		}else{
			//start the generation with static parameters
			generateEvents(this.out.getPath(), 1d, CLI.STATS.getLinkageDegree(), CLI.STATS.getStructuredness());
		}

		
		//shutting down properly
		shutdown();
	}
	
	private String plotFileName(int bw, double linkageDegree, double coherence){
		StringBuilder name = new StringBuilder("bw_").append(bw)
								.append("_ld_").append(linkageDegree)
								.append("_ch_").append(coherence);
		return name.toString();
	}
	
	private void generateEvents(String dir, double eventFactor, double ld, double ch){
		
		//the used jitterValues
		//int jitterCounter = 0;
		//long[] jitter = getJitterValues(this.numEvents);
		//the used instances
		int instCounter = 0;
		List<Map<URI, List<URI>>> instances = this.instanceBuffer.getBuffer(eventFactor);
		int eventCounter = 0;
		double[] eventLength = getEventLength(this.numEvents, eventFactor);
		
		IGeneratorOutput out = new FileOutput(dir, plotFileName((int) (eventFactor * eventLength[eventCounter]), ld, ch));
		
		for(int i = 0; i < this.numEvents; i++){
		
			if(eventCounter == eventLength.length){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of eventLenghts.");
				eventLength = getEventLength(this.numEvents - i, eventFactor);
				eventCounter = 0;
			}
			if(instCounter == INSTANCE_BUFFER_SIZE){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of instances.");
				instances = this.instanceBuffer.getBuffer(eventFactor);
				instCounter = 0;
			}
			//generate the event
			
			
			
			generateSingleEvent(instances.get(instCounter++), eventLength[eventCounter++], out);
			if(CLI.log.isLoggable(Level.INFO)) CLI.log.info("Event nr: " + i + " generated.");
			//check all the buffers if we need to refill them

			/*
			if(jitterCounter == jitter.length){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of jitterValues.");
				jitter = getJitterValues(this.numEvents - i);
				jitterCounter = 0;
			}
			try {
				//sleeping the jitter time
				Thread.sleep(jitter[jitterCounter++]);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			*/
		}
	}
	
	
	private void generateSingleEvent(Map<URI, List<URI>> generationInstances, double eventSize, IGeneratorOutput out){
		//optimizing the number of properties and sending it to the output
		this.propOptimizer.optimizePropertyOutput(eventSize, generationInstances, out);
	}
	
	private double[] getEventLength(int eventsLeft, double factor){
		int numValues;
		if(eventsLeft < EVENT_SIZE_BUFFER_SIZE){
			numValues = eventsLeft;
		}else{
			numValues = EVENT_SIZE_BUFFER_SIZE;
		}
		// getting the event lengths
		double[] eventLength = this.distribValues.getDistributionValue(CLI.STATS.getBandwidthDistribution(), numValues);
		for(int i = 0; i < eventLength.length; i++){
			eventLength[i] = eventLength[i] * factor;
		}
		return eventLength;
	}
	
	private long[] getJitterValues(int eventsLeft){
		int numValues;
		if(eventsLeft < JITTER_BUFFER_SIZE){
			numValues = eventsLeft;
		}else{
			numValues = JITTER_BUFFER_SIZE;
		}
		// getting the jitter values
		double[] doubleJitter = this.distribValues.getDistributionValue(CLI.STATS.getJitterDistribution(), numValues);
		long[] jitter = new long[numValues];
		//we needs to convert them to long
		for(int i = 0; i < jitter.length; i++){
			jitter[i] = Math.abs((long) doubleJitter[i]);
		}
		return jitter;
	}
	
	public void shutdown(){
		if(this.out != null){
			this.out.close();
		}
		
		this.distribValues.shutdown();
		if(!(this.distribValues == this.optSolver)){
			this.optSolver.shutdown();
		}
		this.instanceBuffer.shutdown();
		this.propOptimizer.shutdown();
	}
}
